
#include "znx_http_site_mgr.h"
#include "znx_core_conf.h"
#include "comm/znx_crc.h"

#define ZNX_HTTP_SITE_MGR_META_SLOT     4
#define ZNX_HTTP_SITE_MGR_META_BUCKET   8
#define ZNX_HTTP_SITE_MGR_SLOT          32
// total cache item = ZNX_HTTP_SITE_MGR_SLOT * ZNX_HTTP_SITE_CACHE_ITEM
#define ZNX_HTTP_SITE_CACHE_ITEM        128
#define ZNX_HTTP_SITE_MIN_CACHE_TIME    10000   // 10s


znx_http_site_mgr_meta_t        *g_http_site_mgr_meta[ZNX_HTTP_SITE_MGR_META_SLOT];
znx_http_site_mgr_t             *g_http_site_mgr[ZNX_HTTP_SITE_MGR_SLOT];


static void
znx_http_site_meta_node_free(znx_hash_node_t *node)
{
    znx_http_site_meta_item_t   *item = (znx_http_site_meta_item_t *)node;
    znx_thread_free(item);
}


void
znx_http_site_meta_insert(znx_str_t *host, int64_t version)
{
    uint32_t                    crc;
    uint32_t                    idx;
    znx_hash_node_t             *node;
    znx_http_site_meta_item_t   *item;

    crc = znx_crc32_long(host->data, host->len);
    idx = crc % ZNX_HTTP_SITE_MGR_META_SLOT;

    znx_http_site_meta_item_t   target;
    target.node.hash_key = crc;
    target.host = *host;

    znx_rwlock_wlock(&g_http_site_mgr_meta[idx]->rwlock);

    node = znx_hash_tab_get(g_http_site_mgr_meta[idx]->meta, &target.node);
    if (node) {
        item = (znx_http_site_meta_item_t *)node;
        item->version = version;
    } else {
        item = znx_thread_malloc(sizeof(znx_http_site_meta_item_t) + host->len + 1);
        item->node.hash_key = crc;
        item->host.data = (u_char *)(item) + sizeof(znx_http_site_meta_item_t);
        znx_memcpy(item->host.data, host->data, host->len);
        item->host.len = host->len;
        item->host.data[host->len] = '\0';
        znx_hash_tab_add(g_http_site_mgr_meta[idx]->meta, &item->node);
    }

    znx_rwlock_wunlock(&g_http_site_mgr_meta[idx]->rwlock);
}


znx_bool_t
znx_http_site_meta_lookup(znx_str_t *host, int64_t *version)
{
    uint32_t                    crc;
    uint32_t                    idx;
    znx_hash_node_t             *node;
    znx_http_site_meta_item_t   *item;
    znx_bool_t                  ret = ZNX_FALSE;

    crc = znx_crc32_long(host->data, host->len);
    idx = crc % ZNX_HTTP_SITE_MGR_META_SLOT;

    znx_http_site_meta_item_t   target;
    target.node.hash_key = crc;
    target.host = *host;

    znx_rwlock_rlock(&g_http_site_mgr_meta[idx]->rwlock);

    node = znx_hash_tab_get(g_http_site_mgr_meta[idx]->meta, &target.node);
    if (node) {
        ret = ZNX_TRUE;
        item = (znx_http_site_meta_item_t *)node;
        *version = item->version;
    }

    znx_rwlock_runlock(&g_http_site_mgr_meta[idx]->rwlock);

    return ret;
}


void
znx_http_site_meta_delete(znx_str_t *host)
{
    uint32_t                    crc;
    uint32_t                    idx;
    znx_hash_node_t             *node;

    crc = znx_crc32_long(host->data, host->len);
    idx = crc % ZNX_HTTP_SITE_MGR_META_SLOT;

    znx_http_site_meta_item_t   target;
    target.node.hash_key = crc;
    target.host = *host;

    znx_rwlock_wlock(&g_http_site_mgr_meta[idx]->rwlock);

    node = znx_hash_tab_remove(g_http_site_mgr_meta[idx]->meta, &target.node);
    if (node) {
        znx_http_site_meta_node_free(node);
    }

    znx_rwlock_wunlock(&g_http_site_mgr_meta[idx]->rwlock);
}


void
znx_http_site_mgr_insert(znx_http_site_conf_t *site_conf)
{
    uint32_t                    idx;

    idx = site_conf->crc % ZNX_HTTP_SITE_MGR_SLOT;

    znx_spinlock_lock(&g_http_site_mgr[idx]->lock);
    znx_slru_put(g_http_site_mgr[idx]->slru, site_conf);
    znx_spinlock_unlock(&g_http_site_mgr[idx]->lock);
}


void
znx_http_site_mgr_delete(znx_str_t *host)
{
    uint32_t                    idx;
    uint32_t                    crc;

    crc = znx_crc32_long(host->data, host->len);
    idx = crc % ZNX_HTTP_SITE_MGR_SLOT;

    znx_http_site_conf_t        target;
    target.crc = crc;
    target.server_name = *host;

    znx_spinlock_lock(&g_http_site_mgr[idx]->lock);
    znx_slru_remove(g_http_site_mgr[idx]->slru, &target);
    znx_spinlock_unlock(&g_http_site_mgr[idx]->lock);
}


static znx_http_site_conf_t *
znx_http_site_mgr_lookup_internal(znx_str_t *host)
{
    uint32_t                    idx;
    uint32_t                    crc;
    void                        *data;
    znx_http_site_conf_t        *site_conf = NULL;

    crc = znx_crc32_long(host->data, host->len);
    idx = crc % ZNX_HTTP_SITE_MGR_SLOT;

    znx_http_site_conf_t        target;
    target.crc = crc;
    target.server_name = *host;

    znx_spinlock_lock(&g_http_site_mgr[idx]->lock);
    if (znx_slru_get(g_http_site_mgr[idx]->slru, &target, &data)) {
        site_conf = data;
        znx_http_site_conf_hold(site_conf);
    }
    znx_spinlock_unlock(&g_http_site_mgr[idx]->lock);

    return site_conf;
}


znx_http_site_conf_t *
znx_http_site_mgr_lookup(znx_str_t *host, znx_bool_t *not_found, char **err)
{
    znx_http_site_conf_t    *site_conf, *new_site_conf;
    int64_t                 version = 0;
    int64_t                 now_ms = 0;

    *not_found = ZNX_FALSE;
    *err = NULL;

    if (!znx_http_site_meta_lookup(host, &version)) {
        *not_found = ZNX_TRUE;
        return NULL;
    }

    site_conf = znx_http_site_mgr_lookup_internal(host);
    if (site_conf) {
        now_ms = znx_usec_2_msec(get_cache_time());
        if (site_conf->version == version || site_conf->expire_ms > now_ms) {
            return site_conf;
        }
    }

    new_site_conf = znx_http_site_conf_create();

    char                    filepath[2048];
    znx_str_t               *http_sites_enabled_dir;
    http_sites_enabled_dir = &g_core_conf->http_sites_enabled_dir;

    if (http_sites_enabled_dir->len + host->len + 32 > 2048) {
        *err = "conf file path is too long";
        return site_conf;
    }

    znx_snprintf((u_char *)filepath, 2048, "%v%v.yaml", http_sites_enabled_dir, host);

    if (!znx_http_site_conf_parse(new_site_conf, filepath, host)) {
        *err = "parse site conf failed";
        znx_http_site_conf_free(new_site_conf);

        // if the old configuration is available,
        // return the old configuration.
        return site_conf;
    }

    if (site_conf) {
        znx_http_site_conf_free(site_conf);
    }

    now_ms = znx_usec_2_msec(get_cache_time());
    new_site_conf->expire_ms = now_ms + ZNX_HTTP_SITE_MIN_CACHE_TIME;

    znx_http_site_mgr_insert(new_site_conf);
    znx_http_site_conf_hold(new_site_conf);
    return new_site_conf;
}


void
znx_http_site_mgr_init()
{
    for (int i = 0; i < ZNX_HTTP_SITE_MGR_META_SLOT; i++) {
        g_http_site_mgr_meta[i] = znx_thread_calloc(sizeof(znx_http_site_mgr_meta_t));
        znx_rwlock_init(&g_http_site_mgr_meta[i]->rwlock);

        g_http_site_mgr_meta[i]->meta = znx_hash_tab_create(
            ZNX_HTTP_SITE_MGR_META_BUCKET, 1024,
            znx_http_site_meta_item_cmp,
            znx_http_site_meta_node_free);
    }

    for (int i = 0; i < ZNX_HTTP_SITE_MGR_SLOT; i++) {
        g_http_site_mgr[i] = znx_thread_calloc(sizeof(znx_http_site_mgr_t));
        znx_spinlock_init(&g_http_site_mgr[i]->lock);
        g_http_site_mgr[i]->slru = znx_slru_create(2,
            ZNX_HTTP_SITE_CACHE_ITEM,
            0, NULL,
            znx_http_site_conf_free_handle,
            znx_http_site_conf_cmp_handle);
    }
}


void
znx_http_site_mgr_destroy()
{
    for (int i = 0; i < ZNX_HTTP_SITE_MGR_META_SLOT; i++) {
        znx_hash_tab_destroy(g_http_site_mgr_meta[i]->meta);
        znx_thread_free(g_http_site_mgr_meta[i]);
    }

    for (int i = 0; i < ZNX_HTTP_SITE_MGR_SLOT; i++) {
        znx_slru_destroy(g_http_site_mgr[i]->slru);
        znx_thread_free(g_http_site_mgr[i]);
    }
}


znx_http_addr_site_conf_t *
znx_http_add_site_lookup_in_site_conf(znx_http_site_conf_t *site_conf,
    znx_address_t *listen_address)
{
    znx_http_addr_site_conf_t   *def_addr_site = NULL;
    znx_http_addr_site_conf_t   *addr_site, *addr_sitep;
    znx_address_t               *address, *addressp;

    if (site_conf->addr_sites == NULL) {
        return NULL;
    }

    addr_sitep = site_conf->addr_sites->elts;
    for (size_t i = 0; i < site_conf->addr_sites->nelts; i++) {
        addr_site = addr_sitep + i;
        def_addr_site = addr_site; // the last addr_site is def_addr_site.
        if (addr_site->address_arr && listen_address) {
            addressp = addr_site->address_arr->elts;
            for (size_t j = 0; j < addr_site->address_arr->nelts; j++) {
                address = addressp + j;
                if (znx_memcmp(address, listen_address, sizeof(znx_address_t)) == 0) {
                    return addr_site;
                }
            }
        }
    }

    return def_addr_site;
}


znx_http_addr_site_conf_t *
znx_http_addr_site_lookup(znx_str_t *host, znx_address_t *listen_address,
    znx_bool_t *not_found, char **err)
{
    znx_http_site_conf_t        *site_conf;
    znx_http_addr_site_conf_t   *addr_site;
    znx_str_t                   tmp_host;
    size_t                      len;
    u_char                      buf[320] = { 0 };
    u_char                      *src = host->data;
    u_char                      *src_end = src + host->len;

    if (host->len > 256) {
        *err = "host is too long";
        return NULL;
    }

    *err = NULL;
    *not_found = ZNX_FALSE;

    site_conf = znx_http_site_mgr_lookup(host, not_found, err);
    if (site_conf) {
        addr_site = znx_http_add_site_lookup_in_site_conf(site_conf, listen_address);
        if (addr_site) {
            return addr_site;
        }
        znx_http_site_conf_free(site_conf);
        *err = "not_found addr_site in site_conf";
        return NULL;
    } else if (*err != NULL) {
        return NULL;
    }

    // Avoid staring with '.'
    src++;

    int depth = g_http_main_conf->lookup_depth;
    for (int i = 0; i < depth; i++) {
        while (src < src_end) {
            if (*src == '.') {
                break;
            }
            src++;
        }

        if (src == src_end) {
            break;
        }

        buf[0] = '*';
        len = (size_t)(src_end - src);
        znx_memcpy(buf+1, src, len);
        buf[len+1] = '\0';
        tmp_host.data = buf;
        tmp_host.len = len + 1;

        // skip char '.'
        src++;
        site_conf = znx_http_site_mgr_lookup(&tmp_host, not_found, err);
        if (site_conf) {
            addr_site = znx_http_add_site_lookup_in_site_conf(site_conf, listen_address);
            if (addr_site) {
                return addr_site;
            }
            znx_http_site_conf_free(site_conf);
            *err = "not_found addr_site in site_conf";
            return NULL;
        } else if (*err != NULL) {
            return NULL;
        }
    }

    // last, get default site_conf.

    *err = NULL;
    tmp_host.data = (u_char *)"*";
    tmp_host.len = 1;
    site_conf = znx_http_site_mgr_lookup(&tmp_host, not_found, err);
    if (site_conf) {
        addr_site = znx_http_add_site_lookup_in_site_conf(site_conf, listen_address);
        if (addr_site) {
            return addr_site;
        }
        znx_http_site_conf_free(site_conf);
        *err = "not found addr_site in site_conf";
        return NULL;
    } else if (*err != NULL) {
        return NULL;
    }

    // unreachable.
    return NULL;
}

